package bplustree;

import java.io.IOException;
import java.util.LinkedList;
import java.util.ListIterator;

import FilesManagers.FileManager;

import register.Register;
import register.RegisterID;

/**
 * Clase que representa a un nodo genérico del arbol B+
 * 
 */

public class Node {
	
	protected BPlusTree tree;
	protected int node_size; //bytes
	protected int free_space;
	protected int level;
	protected int position;
	protected int register_count;
	protected boolean modified;
	
	protected LinkedList<Integer> childrenList; 
	protected LinkedList<Register> registerList;
	
	
	/**
	 * Constructor, como parametro necesita recibir el nivel del nodo y el arbol al que pertenece
	 * 
	 * @param level
	 * @param tree
	 */
	
	public Node(int level, BPlusTree tree) {
		modified=true;
		this.level=level;
		this.tree=tree;
		position=-1;
		register_count=0;
		node_size=tree.getNode_size();
		childrenList=new LinkedList<Integer>();
		registerList=new LinkedList<Register>();
	}

	
	
	/**
	 * devuelve el valor de modified.
	 * Seteado a true si el registro fue modificado desde la ultima escritura.
	 * 
	 * @return the modified
	 */
	public boolean isModified() {
		return modified;
	}



	/**
	 * @param modified the modified to set
	 */
	public void setModified(boolean modified) {
		this.modified = modified;
	}



	/**
	 * Persiste el nodo hijo pasado como parametro. Es importante la condición de hijo ya que la usa para
	 * calculo de nivel. Ya que siempre se escriben los hijos la función no debe ser llamada desde las hojas
	 * 
	 * En el caso de estar en nivel 1 la escritura de los registros se realiza utilizando codificación gamma.
	 * 
	 * La estructura de escritura de nodos y hojas es la misma. En el caso de los nodos los registros no contienen
	 * atributos. En el caso de las hojas no hay punteros a hijos.
	 * 
	 * @throws IOException 
	 *
	 */
	
	public void writeChild(Node node) throws IOException{
		if(node.position<0) node.position=tree.getFree_positions();
			tree.getFile().seek(node.position*node.node_size);
		ListIterator<Register> iterReg=node.registerList.listIterator();
		ListIterator<Integer> iterChild=node.childrenList.listIterator();
				
		while(iterChild.hasNext()){
				tree.getFile().writeBytes(iterChild.next().toString().concat(String.valueOf(tree.getSeparatorAttribute())));
			
			
		}
		tree.getFile().writeBytes(String.valueOf(tree.getSeparatorField()));
		
		if(level==1){ //estoy escribiendo una hoja
			
			tree.getFile().writeBytes(Integer.toString(((Leaf) node).getPreviousLeaf()).concat(String.valueOf(tree.getSeparatorField())));
			tree.getFile().writeBytes(Integer.toString(((Leaf) node).getNextLeaf()).concat(String.valueOf(tree.getSeparatorField())));
				//INSERTO CONTADOR DE REGISTROS NECESARIO PARA EL GAMMA CODING
			tree.getFile().writeBytes(Integer.toString(node.getRegister_count()).concat(String.valueOf(tree.getSeparatorField())));
				//INSERTO CONTADOR DE REGISTROS NECESARIO PARA EL GAMMA CODING
			
			while(iterReg.hasNext()){				
				String output=iterReg.next().writeGamma();				
				tree.getFile().writeBytes(output);				
			}			
		}		
		else{ //estoy escribiendo un nodo interno
			while(iterReg.hasNext()){
				tree.getFile().writeBytes(iterReg.next().write().concat(String.valueOf(tree.getSeparatorAttribute())));
			}
			tree.getFile().writeBytes(String.valueOf(tree.getSeparatorField()));
		}
		
		while(tree.getFile().getFilePointer()<(node.position+1)*node.node_size){
			tree.getFile().writeBytes("-"); //rellena con espacios
		}
		
		node.setModified(false); //acabo de escribirlo;
	}
	
	/**
	 * 
	 * Devuelve la posición del mayor identificador menor o igual en la lista.
	 * 
	 * @param ID
	 * @return
	 */
	protected int getImmediateLess(RegisterID ID){
		ListIterator<Register> iterador=registerList.listIterator();
		RegisterID tempID;
		int position=-1;
		while(iterador.hasNext()){
			tempID=iterador.next().getID();
			position++;
			if(tempID.compareTo(ID)>0) return position-1; 
			//si encuentro uno tal que es mayor, entonces devuelvo el anterior que va a ser el ultimo menor
		}
		return position; //todo otro caso, devuelvo la posicion (quiere decir que me pase)
	}
	
	/**
	 * Método que devuelve el espacio libre en el nodo. Devuelve el espacio comparando la longitúd de
	 * impresión de los contenidos con el tamaño del nodo.
	 * 
	 * @return the free_space
	 */
	public int getFree_space() { //OJO, POR AHORA TOMO EN CUENTA UNICAMENTE EL TAMANIO DE LOS REGISTROS
		ListIterator<Register> iterador=registerList.listIterator();
		ListIterator<Integer> iterador2=childrenList.listIterator();
		
		free_space=node_size;
		
		while(iterador.hasNext()&&!registerList.isEmpty()){ //El isEmpty es redundante
			free_space-=(iterador.next().write().length()+1); //el 1 corresponde al separador de registros
		}
		free_space--; //corresponde al tamanio del separador de campo
		
		
		while(iterador2.hasNext()&&!childrenList.isEmpty()){ //El isEmpty es redundante
			free_space-=(iterador2.next().toString().length()+1); //el 1 corresponde al separador de registros
		}
		free_space--; //corresponde al tamanio del separador de campo
		
		return free_space;
	}
	
	/**
	 * @param free_space the free_space to set
	 */
	public void setFree_space(int free_space) {
		this.free_space = free_space;
	}
	
	/**
	 * @return the node_size
	 */
	public int getNode_size() {
		return node_size;
	}
	/**
	 * @param node_size the node_size to set
	 */
	public void setNode_size(int node_size) {
		this.node_size = node_size;
	}
	
	/**
	 * @return the level
	 */
	public int getLevel() {
		return level;
	}
	/**
	 * @param level the level to set
	 */
	public void setLevel(int level) {
		this.level = level;
	}
	/**
	 * @return the position
	 */
	public int getPosition() {
		return position;
	}
	/**
	 * @param position the position to set
	 */
	public void setPosition(int position) {
		this.position = position;
	}
	/**
	 * @return the register_count
	 */
	public int getRegister_count() {
		return registerList.size();
	}
	/**
	 * @param register_count the register_count to set
	 */
	public void setRegister_count(int register_count) {
		this.register_count = register_count;
	}

	/**
	 * Inserta un registro en el nodo. Función recursiva que busca el camino a seguir comparando
	 * el identificador con los almacenados según comportamiento previsto para arbol B+.
	 * 
	 * En caso de recibir Excepción de overflow de un hijo se encarga de hacer el SPLIT. Si esto
	 * genera a se vez overflow en si mismo, arroja otra excepción.
	 * 
	 * En caso de recibir una excepción de registro duplicado, la arroja hacia arriba.
	 * 
	 * @param reg
	 * @throws NodeOverflowException
	 * @throws IOException
	 * @throws DuplicateRegisterException
	 */
	protected void insert(Register reg) throws NodeOverflowException, IOException, DuplicateRegisterException {
		//hago get ImmediateLess, del valor resuelto tengo que ir para el de la derecha...puede ser -1 en cuyo caso tengo que ir a 0
		int tempPosition = getImmediateLess(reg.getID())+1;
		Node node = new Leaf(tree); //despues se elimina de todos modos
		try {
			node=loadChild(childrenList.get(tempPosition));
			node.insert(reg);
		} catch (NodeOverflowException e) {
			NodeOverflowExceptionHandler(node,tempPosition);
			this.modified=true;
			if(getFree_space()<0){
				//INSERCION CON OVERFLOWWW
					throw new NodeOverflowException();
			}
			//Node superNode=new Node(level+1,tree);
			//superNode.writeChild(this);
		}
		if(node.isModified()){//solo lo escribe si fue modificado
			writeChild(node);
			
		}
	}

	
	/**
	 * modifica un registro en el nodo. Función recursiva que busca el camino a seguir comparando
	 * el identificador con los almacenados según comportamiento previsto para arbol B+.
	 * 
	 * En caso de recibir Excepción de overflow de un hijo se encarga de hacer el SPLIT. Si esto
	 * genera a se vez overflow en si mismo, arroja otra excepción.
	 * 
	 * @throws IOException
	 * @throws NodeOverflowException
	 */
	public void modify(Register reg) throws IOException, NodeOverflowException {
//		hago get ImmediateLess, del valor resuelto tengo que ir para el de la derecha...puede ser -1 en cuyo caso tengo que ir a 0
		int tempPosition = getImmediateLess(reg.getID())+1;
		Node node=new Leaf(tree); //despues no sirve
		try {
			node=loadChild(childrenList.get(tempPosition));
			node.modify(reg);
		} catch (NodeOverflowException e) {
			
			NodeOverflowExceptionHandler(node,tempPosition);
			this.modified=true;
			if(getFree_space()<0){
				//INSERCION CON OVERFLOWWW
					throw new NodeOverflowException();
			}
			Node superNode=new Node(level+1,tree);
			superNode.writeChild(this);
		}
		if(node.isModified()) // solo lo escribe si fue modificado
			writeChild(node);
	}

	Register removeFirstReg() {
		return registerList.removeFirst();
	}

	Register getFirstReg() {
		return registerList.getFirst();
	}

	void setFirstReg(Register firstReg) {
		registerList.addFirst(firstReg);
	}

	Register removeLastReg() {
		return registerList.removeLast();
	}

	void setFirstChild(int firstChild) {
		childrenList.addFirst(firstChild);
	}

	int removeLastChild() {
		if (childrenList.isEmpty()) return 0; //caso de lista vacia, si no tira exception
		return childrenList.removeLast();
	}

	/**
	 * Busca un registro en el nodo. Función recursiva que busca el camino a seguir comparando
	 * el identificador con los almacenados según comportamiento previsto para arbol B+.
	 * 
	 * Setea el iterador de registros pasado como parametro para apuntar al registro buscado.
	 * 
	 * @param ID
	 * @param iterator
	 * @return
	 * @throws IOException
	 */
	public Register search(RegisterID ID,RegisterIterator iterator) throws IOException {
		return loadChild(childrenList.get(getImmediateLess(ID)+1)).search(ID,iterator);
	}

	/**
	 * Devuelve un iterador de registro al primer registro del arbol
	 * A ser usado para el listado.
	 * 
	 * @return
	 * @throws IOException
	 */
	public RegisterIterator firstRegIterator() throws IOException {
		return loadChild(childrenList.get(0)).firstRegIterator();
	}
	
	/**
	 * Carga el nodo hijo persistido en la posición relativa de archivo pasada como parametro.
	 * Es importante la condición de hijo ya que la usa para
	 * calculo de nivel. Ya que siempre se escriben los hijos la función no debe ser llamada desde las hojas
	 * 
	 * En el caso de estar en nivel 1 la lectura de los registros se realiza utilizando codificación gamma.
	 * 
	 * La estructura de escritura de nodos y hojas es la misma. En el caso de los nodos los registros no contienen
	 * atributos. En el caso de las hojas no hay punteros a hijos
	 * @param position
	 * @return
	 * @throws IOException
	 */
	
	public Node loadChild(int position) throws IOException{
		Node outputNode;
		if(level==1) outputNode=new Leaf(tree);
		else outputNode=new Node(level-1,tree);
		
		outputNode.position=position;
		tree.getFile().seek(position*node_size);
		String auxString;
		String[] subStrings;
		String temp;
		Register auxReg;
		
		
		//children ready
		
		if(level==1){
			tree.getFile().read();//Leo solo la @
			
			auxString=tree.getFile().readField();//La hoja previa
			((Leaf) outputNode).setPreviousLeaf(Integer.valueOf(auxString));
			
			auxString=tree.getFile().readField();//La hoja siguiente
			((Leaf) outputNode).setNextLeaf(Integer.valueOf(auxString));
			
			auxString=tree.getFile().readField();//CORRESPONDIENTE AL NUMERO DE REGISTROS
												//VA A SER USADO CON EL GAMMA CODING
			
			((Leaf) outputNode).setRegister_count(Integer.valueOf(auxString));
		
			Register outputRegister=new Register();
			int cantRegGamma=outputNode.register_count;////Modifique__revisar y volver cambio atras
			for(;cantRegGamma>0;cantRegGamma--){
				outputRegister=new Register();
				
				outputRegister.loadGamma(tree.getFile());
				
				
				outputNode.registerList.addLast(outputRegister);
			}
			//FileManager.append("treeLog.txt","\n Lei en Leaf5 "+outputRegister.writeGamma()+" \n");
			
		
		}
		else{
			auxString=tree.getFile().readField();
			
			temp=""+tree.getSeparatorAttribute();
			//auxString=auxString.replace("##","#$");
			subStrings=auxString.split(temp);
			for(int i=0; i<subStrings.length; i++){
				
				outputNode.childrenList.addLast(Integer.valueOf(subStrings[i]));
			}
			auxString=tree.getFile().readField();
			temp=""+tree.getSeparatorAttribute();
			subStrings=auxString.split("#"+temp);
			//FileManager.append("treeLog.txt","\n Lei en NOde "+auxString+" \n");
			
			if(subStrings[0].compareTo("")!=0){ //implica que no es nodo interno ni hoja vacia
				for(int i=0; i<subStrings.length; i++){
					auxReg=new Register();
					auxReg.load(subStrings[i]);
					//FileManager.append("treeLog.txt","\n Lei en NOde2 "+subStrings[i]+" \n");
					
					outputNode.registerList.addLast(auxReg);
				}
			}
		}
		
		
		outputNode.setModified(false); //si recien lo cargo no fue modificado
		return outputNode;
	}

	public BPlusTree getTree(){
		return tree;
	}
	 /**
	  * Maneja la el overflow del nodo pasado como parámetro que se espera que sea hijo
	  * Como parámetro recibe la posición del nodo en la LISTA DE HIJOS, no es la posicion en el archivo.
	  * @param tempPosition
	  * @throws IOException
	  */
	private void NodeOverflowExceptionHandler(Node node, int tempPosition) throws IOException{
		
		Register tempReg;
		Node tempNode;
		setModified(true); //si un hijo hace un split, entonces el padre seguro se modifica
		if (getLevel()==1) tempNode=new Leaf(tree);
		else tempNode=new Node(node.getLevel(),tree);
				
		Node superLeaf=new Node(1,tree); //usado para escribir las hojas
				
		while(tempNode.getFree_space()>tempNode.getNode_size()/2){
			
			if(!node.registerList.isEmpty()) tempNode.setFirstReg(node.removeLastReg());
			if(!node.childrenList.isEmpty()) tempNode.setFirstChild(node.removeLastChild());
		}
		
		
		if (getLevel()==1){ // si lo que se partio es una hoja copia el Id del primer registro
			//TODO Agregarle un nodo artificial como first children pero no creo que sea necesario, si hay null no pasa nada
			tempReg= new Register(tempNode.getFirstReg().getID());
			//mantenimiento del encadenamiento de las hojas
			Leaf tempPrevLeaf, tempLeaf, tempNextLeaf;
			
			tempLeaf=(Leaf)tempNode;
			tempPrevLeaf=(Leaf)node;
			if(tempPrevLeaf.getNextLeaf()!=node.getPosition()){
				tempNextLeaf=(Leaf)superLeaf.loadChild(tempPrevLeaf.getNextLeaf());
			}
			else 
				tempNextLeaf=(Leaf) node;
			
			tempLeaf.setPreviousLeaf(tempPrevLeaf.getPosition());
			tempLeaf.setNextLeaf(tempNextLeaf.getPosition());
			if(tempLeaf.position<0) tempLeaf.position=tree.getFree_positions();//Me ahorro de hacer un write
			tempPrevLeaf.setNextLeaf(tempLeaf.getPosition());
			tempNextLeaf.setPreviousLeaf(tempLeaf.getPosition());
			superLeaf.writeChild(tempNextLeaf);
			//termina mantenimiento del encadenamiento de las hojas
			
		}
		else {		// si lo que se partio es un nodo interno promueve el primer registro
			tempReg=tempNode.removeFirstReg();
		}
		writeChild(tempNode); //ojo! El orden es importante porque necesito que se le asigne una posicion antes de ponersela al padre
		
		registerList.add(tempPosition, tempReg);	
		childrenList.add(tempPosition+1, tempNode.getPosition());
		
		writeChild(node);
	
		
	}
	
}
